﻿//-----------------------------------------------------------------------
// <copyright file="RepositoryBase.cs" company="FastNET Services">
//     Copyright (c) 2007-2011. All Rights Reserved.
// </copyright>
// <warranty>
//     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
//     BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
//     DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </warranty>
//-----------------------------------------------------------------------
#region Usings

using System;
using System.Collections.Generic;
using System.Data.EntityClient;
using System.Data.Objects;
using System.Linq;
using FastNet.RepositorySystem.Interfaces;

#endregion

namespace FastNet.RepositorySystem
{
    /// <summary>
    /// Base Abstract class for a entity framework 4 based repository
    /// </summary>
    /// <typeparam name="TData">The RepositoryData interface for the associated object</typeparam>
    /// <typeparam name="TObject">The Entity Framework object being managed by this repository</typeparam>
    public abstract class RepositoryBase<TData, TObject>
        : IRepository<TData>
        where TData : IData
        where TObject : class, TData
    {
        #region CTor/DTor

        /// <summary>
        /// Initializes a new instance of the RepositoryBase class
        /// </summary>
        /// <param name="connection">EntityConnection used for connecting to the database</param>
        /// <exception cref="ArgumentNullException">Thrown if connection is null</exception>
        protected RepositoryBase(EntityConnection connection)
        {
            if (connection == null) throw new ArgumentNullException("connection");
            Connection = connection;
        }

        #endregion

        #region Required Overrides

        /// <summary>
        /// Gets the entity set name for the type of object being managed
        /// </summary>
        /// <returns>String representing the Entity Set name</returns>
        protected abstract string EntitySetName { get; }

        /// <summary>
        /// Requests the specified object to be modified for a saved deletion status
        /// </summary>
        /// <param name="item">Item to modify</param>
        protected abstract void MarkObjectDeleted(TObject item);

        #endregion

        #region Protected Properties

        /// <summary>
        /// Gets a reference to the common EntityConnection for the repository
        /// </summary>
        protected EntityConnection Connection { get; private set; }

        #endregion

        #region Repository Members

        /// <summary>
        /// Deletes all data from this repository.
        /// </summary>
        public void Delete()
        {
            using (ObjectContext entities = new ObjectContext(Connection))
            {
                //TODO Handle Concurrency
                IEnumerable<TObject> data =
                    (from TObject entity in entities.CreateObjectSet<TObject>() where !entity.DateDeleted.HasValue select entity);

                foreach (TObject item in data)
                    MarkObjectDeleted(item);

                entities.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
            }
        }

        /// <summary>
        /// Deletes all data from this repository.
        /// </summary>
        /// <param name="permanently">Boolean whether or not to permanently remove the data or to just mark it for cleanup later</param>
        public void Delete(bool permanently)
        {
            if (!permanently)
            {
                Delete();
                return;
            }

            using (ObjectContext entities = new ObjectContext(Connection))
            {
                //TODO Handle Concurrency
                IEnumerable<TObject> data =
                    (from TObject entity in entities.CreateObjectSet<TObject>() select entity);

                foreach (TObject item in data)
                    entities.DeleteObject(item);

                entities.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
            }
        }

        /// <summary>
        /// Retrieves all instances of <typeparamref name="TData"/> from the repository
        /// </summary>
        /// <returns>IEnumerable of the requested <typeparamref name="TData"/></returns>
        public IList<TData> Retrieve()
        {
            using (ObjectContext entities = new ObjectContext(Connection))
            {
                var returnItems = (from TObject entity
                        in entities.CreateObjectSet<TObject>()
                                   where !entity.DateDeleted.HasValue
                                   select entity);
                return Enumerable.Select(returnItems, item => (TData) item).ToList();
            }
        }

        /// <summary>
        /// Retrieves all instances of <typeparamref name="TData"/> from the repository
        /// </summary>
        /// <param name="includeDeleted">Boolean whether or not to include deleted entries</param>
        /// <returns>IEnumerable of the requested <typeparamref name="TData"/></returns>
        public IList<TData> Retrieve(bool includeDeleted)
        {
            if (!includeDeleted)
                return Retrieve();

            using (ObjectContext entities = new ObjectContext(Connection))
            {
                return (from TObject entity
                        in entities.CreateObjectSet<TObject>()
                        select entity).Cast<TData>().ToList();
            }
        }

        /// <summary>
        /// Deletes the requested <typeparamref name="TData"/>
        /// </summary>
        /// <param name="data">A <typeparamref name="TData"/> to delete from the repository</param>
        /// <exception cref="ArgumentNullException">Thrown if data is null</exception>
        /// <exception cref="InvalidCastException">Thrown if the data is of the wrong type for this data source</exception>
        /// <exception cref="InvalidOperationException">Thrown if the <typeparamref name="TData"/> is new or already deleted</exception>
        public void Delete(TData data)
        {
            // ReSharper disable CompareNonConstrainedGenericWithNull
            if (data == null)
                // ReSharper restore CompareNonConstrainedGenericWithNull
                throw new ArgumentNullException("data");

            if (data.IsNew || data.IsDeleted)
                throw new InvalidOperationException("Data is new or already deleted");

            TObject entity = data as TObject;

            if (entity == null)
                throw new InvalidCastException("Data parameter was not from this repository");

            MarkObjectDeleted(entity);

            using (ObjectContext entities = new ObjectContext(Connection))
            {
                //TODO Handle Concurrency
                HandleChanges(entity);
                entities.ApplyCurrentValues(EntitySetName, entity);
                entities.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
            }
        }

        /// <summary>
        /// Deletes the requested <typeparamref name="TData"/>
        /// </summary>
        /// <param name="data">A <typeparamref name="TData"/> to delete from the repository</param>
        /// <param name="permanently">Boolean whether or not to permanently remove the data or to just mark it for cleanup later</param>
        /// <exception cref="ArgumentNullException">Thrown if data is null</exception>
        /// <exception cref="InvalidCastException">Thrown if the data is of the wrong type for this data source</exception>
        /// <exception cref="InvalidOperationException">Thrown if the <typeparamref name="TData"/> is new or already deleted</exception>
        public void Delete(TData data, bool permanently)
        {
            if (!permanently)
            {
                Delete(data);
                return;
            }

            // ReSharper disable CompareNonConstrainedGenericWithNull
            if (data == null)
                // ReSharper restore CompareNonConstrainedGenericWithNull
                throw new ArgumentNullException("data");

            if (data.IsNew || (data.IsDeleted && !data.DateDeleted.HasValue))
                throw new InvalidOperationException("Data is new or already deleted");

            TObject entity = data as TObject;

            if (entity == null)
                throw new InvalidCastException("Data parameter was not from this repository");

            using (ObjectContext entities = new ObjectContext(Connection))
            {
                //TODO Handle Concurrency
                HandleChanges(entity);
                entities.ApplyCurrentValues(EntitySetName, entity);
                entities.DeleteObject(entity);
                entities.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
            }
        }

        /// <summary>
        /// Saves the requested <typeparamref name="TData"/>
        /// </summary>
        /// <param name="data">A <typeparamref name="TData"/> to save to the repository</param>
        /// <exception cref="ArgumentNullException">Thrown if data is null</exception>
        /// <exception cref="InvalidCastException">Thrown if the data is of the wrong type for this data source</exception>
        public void Save(TData data)
        {
            // ReSharper disable CompareNonConstrainedGenericWithNull
            if (data == null)
                // ReSharper restore CompareNonConstrainedGenericWithNull
                throw new ArgumentNullException("data");

            TObject entity = data as TObject;

            if (entity == null)
                throw new InvalidCastException("Data parameter was not from this repository");

            using (ObjectContext entities = new ObjectContext(Connection))
            {
                if (data.IsNew)
                    entities.AddObject(EntitySetName, entity);
                //TODO Handle Concurrency
                HandleChanges(entity);
                if (!data.IsNew)
                    entities.ApplyCurrentValues(EntitySetName, entity);
                entities.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
            }
        }

        /// <summary>
        /// Creates a new instance of <typeparamref name="TData"/>
        /// </summary>
        /// <returns>New Instance of <typeparamref name="TData"/></returns>
        public TData Create()
        {
            using (ObjectContext entities = new ObjectContext(Connection))
            {
                return entities.CreateObject<TObject>();
            }
        }

        #endregion

        #region ApplyChanges

        /// <summary>
        /// Handles merging special configuration changes before saving the entity
        /// </summary>
        /// <param name="entity">Entity to make changes to</param>
        protected virtual void HandleChanges(TObject entity) { }

        #endregion
    }
}
